想要在網路上取得台股最新的股價有許多種方式,其中一種免費的方式就是直接向證交所網站取得股價資料,這次我會分享 3 種用 C# 向證交所取得股價資料的實用方法。
這 3 種方式分別可以應用在不同的實務上,當了解基本取得資料的方式,就可以寫程式定期取得最新或是歷史的股價資料。
先看一次我這次範例的操作畫面。
範例建置環境
後端架構: C# ASP.Net MVC .Net Framework
前端架構: Vue.js, jQuery, Bootstrap
我網頁上只講解重要的程式碼部份,完整範例可至文末下載。
範例畫面輸入要查詢的股票代碼,可輸入單一股票或是用 , (逗號)分隔輸入代碼後按查詢。
按下查詢後,程式會組合查詢網址,例如
https://mis.twse.com.tw/stock/api/getStockInfo.jsp?json=1&delay=0&ex_ch=tse_2330.tw|tse_0050.tw|
網址中的 tse_2330.tw|tse_0050.tw| 就是要查詢的股票名稱,大家可以換成自己需要的股票。
當此網址在瀏覽器查詢時,即會回傳 Json 格式的價格資料
{"msgArray":[{"tv":"-","ps":"-","pz":"-","a":"592.0000_593.0000_594.0000_595.0000_596.0000_","b":"591.0000_590.0000_589.0000_588.0000_587.0000_","c":"2330","d":"20210510","ch":"2330.tw","tlong":"1620614180000","f":"332_721_423_419_366_","ip":"0","g":"586_1044_261_577_263_","mt":"866821","h":"597.0000","i":"24","it":"12","l":"591.0000","n":"台積電","o":"596.0000","p":"0","ex":"tse","s":"-","t":"10:36:20","u":"658.0000","v":"8071","w":"540.0000","nf":"台灣積體電路製造股份有限公司","y":"599.0000","z":"-","ts":"0"},{"tv":"-","ps":"-","nu":"http://www.yuantaetfs.com/#/RtNav/Index","pz":"-","a":"138.7000_138.7500_138.8000_138.8500_138.9000_","b":"138.6500_138.6000_138.5500_138.5000_138.4500_","c":"0050","d":"20210510","ch":"0050.tw","tlong":"1620614179000","f":"13_5_8_120_100_","ip":"0","g":"5_97_4_25_104_","mt":"995922","h":"139.5000","it":"02","l":"138.1500","n":"元大台灣50","o":"139.5000","p":"0","ex":"tse","s":"-","t":"10:36:19","u":"153.3000","v":"2229","w":"125.5000","nf":"元大台灣卓越50證券投資信託基金","y":"139.4000","z":"-","ts":"0"}],"referer":"","userDelay":5000,"rtcode":"0000","queryTime":{"sysDate":"20210510","stockInfoItem":1201,"stockInfo":629093,"sessionStr":"UserSession","sysTime":"10:36:25","showChart":false,"sessionFromTime":-1,"sessionLatestTime":-1},"rtmessage":"OK"}
接下來就可以用程式解析 Json 來取得價格資料。
我列出 Json 內容中價格資訊常用的欄位
在此範例中我只顯示「股票代號/當盤成交價/最低委賣價/最高委買價」做示範,
大家可以依自己的需求調整要顯示的資料。
在呼叫證交所的即時價格時,如果是盤中呼叫,"偶爾" 會發生沒有當盤成交價的問題。
而這問題在逐筆交易上線(2020-3-23)以前,是不會發生的,當時是每 5 秒撮合一次,一定有成交價。
但在逐筆交易上線後,從證交所呼叫即時價格,其實是得到每 5 秒行情快照的結果,也就是 5 秒才會更新一個價格。
可能在快照那當下 0.1 秒時,就是沒有人成交,所以也就沒有看到當盤成交價。
如果沒有當盤成交價的話,只能從委買 1 或委賣 1 來推算可能的成交價了。
如果需要非常即時的逐筆交易價格,我是透過群益 API 來取得即時報價的,可參考這篇文章。
<div class="panel panel-default">
<div class="panel-heading">範例1 即時股價</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2">股票:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_SYMBOL_1.value">
</div>
</div>
<button type="button" class="btn btn-primary" v-on:click="GetRealtimePrice()">查詢</button>
</div>
<div class="panel-footer">
<span v-html="realPrice"></span>
</div>
</div>
只向後端執行呼叫動作
// 即時股價
, GetRealtimePrice: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetRealtimePrice")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.realPrice = datas.realPrice;
hideProcess();
}
}
});
}
利用程式組出要查詢的網址,接著使用 WebClient 取得回傳資料,取得回傳的 Json 資料後就依欄位取出資料。
/// <summary>
/// 即時股價
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetRealtimePrice(FormIn inModel)
{
GetRealtimePriceOut outModel = new GetRealtimePriceOut();
outModel.ErrorMsg = "";
StringBuilder ExCode = new StringBuilder();
string[] symbols = inModel.Q_SYMBOL_1.Split(',');
foreach (string symbol in symbols)
{
ExCode.Append("tse_" + symbol + ".tw|");
}
// 呼叫網址
string url = "https://mis.twse.com.tw/stock/api/getStockInfo.jsp";
url += "?json=1&delay=0&ex_ch=" + ExCode;
string downloadedData = "";
using (WebClient wClient = new WebClient())
{
// 取得網頁資料
wClient.Encoding = Encoding.UTF8;
downloadedData = wClient.DownloadString(url);
}
TwsePriceSchema jsonPrice = null;
if (downloadedData.Trim().Length > 0)
{
jsonPrice = JsonConvert.DeserializeObject<TwsePriceSchema>(downloadedData);
if (jsonPrice.rtcode != "0000")
{
throw new Exception("取商品價格失敗: " + jsonPrice.rtmessage);
}
}
StringBuilder sbRealPrice = new StringBuilder();
for (int i = 0; i < jsonPrice.msgArray.Count; i++)
{
// 代碼
string code = jsonPrice.msgArray[i].c;
// z = 收盤價
string close = jsonPrice.msgArray[i].z;
// a = 最低委賣價
string ask = "";
if (jsonPrice.msgArray[i].a.IndexOf("_") > -1)
{
ask = jsonPrice.msgArray[i].a.Split('_')[0];
}
// b = 最高委買價
string bid = "";
if (jsonPrice.msgArray[i].b.IndexOf("_") > -1)
{
bid = jsonPrice.msgArray[i].b.Split('_')[0];
}
sbRealPrice.Append("代碼: " + code + " 收盤價: " + close + " 最低委賣價: " + ask + " 最高委買價: " + bid + "<br>");
}
outModel.realPrice = sbRealPrice.ToString();
// 輸出json
ContentResult result = new ContentResult();
result.ContentType = "application/json";
result.Content = JsonConvert.SerializeObject(outModel);
return result;
}
程式碼上有簡單的註解說明,若需要實際操作可至下方下載完整範例。
在範例畫面上輸入要查詢的日期,查詢後就會列出當日所有股價的 K 線價格。
當查詢之後,程式會組合查詢網址:
https://www.twse.com.tw/exchangeReport/MI_INDEX?response=csv&date=20210507&type=ALL
替換參數 「20210507」 後就可以改成需要查詢的日期
此查詢結果會回傳 csv 格式的內容,內容包含所有上市交易的清單,以及類股指數的價格,
此檔案比較大有 2 萬多筆資料回傳。
回傳資料分 2 部份,上半部是類股價格而下半部是個股價格,會在同一份檔案中。
我自己是用欄位數量來分辨類股或是個股的。
那接下來就來看程式碼的部份
<div class="panel panel-default">
<div class="panel-heading">範例2 每日收盤行情</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2">日期:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_DATE_2.value">
</div>
</div>
<button type="button" class="btn btn-primary" v-on:click="GetDayPrice()">查詢</button>
</div>
<div class="panel-footer">
<div class="table-responsive" style="overflow-y:auto;height:400px;">
<table class="table">
<tr>
<th>代碼</th>
<th>名稱</th>
<th>開盤價</th>
<th>最高價</th>
<th>最低價</th>
<th>收盤價</th>
<th>成交量</th>
</tr>
<tr v-for="(item, index) in gridDay.datas">
<td>
{{item.symbolCode}}
</td>
<td>
{{item.symbolName}}
</td>
<td>
{{item.open}}
</td>
<td>{{item.high}}</td>
<td>{{item.low}}</td>
<td>{{item.close}}</td>
<td>{{item.volume}}</td>
</tr>
</table>
</div>
</div>
</div>
只向後端執行呼叫動作
// 每日收盤行情
, GetDayPrice: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetDayPrice")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.gridDay.datas = [];
self._RowDataBound(self.gridDay.datas, datas.gridList);
hideProcess();
}
}
});
}
利用程式組出要查詢的網址,接著使用 WebClient 取得回傳資料,取得資料是 CSV 格式,將 CSV 解析成string[] 格式資料,就可以依索引位置來取得指定的資料。
/// <summary>
/// 每日收盤行情
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetDayPrice(FormIn inModel)
{
GetDayPriceOut outModel = new GetDayPriceOut();
outModel.ErrorMsg = "";
// 呼叫網址
string twseUrl = "https://www.twse.com.tw/exchangeReport/MI_INDEX";
string download_url = twseUrl + "?response=csv&date=" + inModel.Q_DATE_2 + "&type=ALL";
string downloadedData = "";
using (WebClient wClient = new WebClient())
{
// 網頁回傳
downloadedData = wClient.DownloadString(download_url);
}
if (downloadedData.Trim().Length > 0)
{
// 回傳前端的資料集
outModel.gridList = new List<StockPriceRow>();
string[] lineStrs = downloadedData.Split('\n');
for (int i = 0; i < lineStrs.Length; i++)
{
string strline = lineStrs[i];
if (strline.Trim().Length == 0)
{
continue;
}
// 排除非價格部份
if (strline.IndexOf("證券代號") > -1 || strline.IndexOf("(元,股)") > -1)
{
continue;
}
if (strline.Substring(0, 1) == "=")
{
strline = strline.TrimStart('=');
}
ArrayList resultLine = new ArrayList();
// 解析資料
this.ParseCSVData(resultLine, strline);
string[] datas = (string[])resultLine.ToArray(typeof(string));
//檢查資料內容
if (datas.Length != 17)
{
continue;
}
// 股票代碼
string symbolCode = datas[0];
if (symbolCode.Length == 4)
{
// 輸出資料
StockPriceRow row = new StockPriceRow();
row.symbolCode = symbolCode; //股票代碼
row.symbolName = datas[1]; //股票名稱
row.open = datas[5]; //開盤價
row.high = datas[6]; //最高價
row.low = datas[7]; //最低價
row.close = datas[8]; //收盤價
row.volume = datas[2]; //成交量
outModel.gridList.Add(row);
}
}
}
// 輸出json
ContentResult result = new ContentResult();
result.ContentType = "application/json";
result.Content = JsonConvert.SerializeObject(outModel); ;
return result;
}
在範例畫面上輸入股票代碼及查詢日期,就可以查詢該股票當月的所有日期價格。
當查詢之後,程式會組合查詢網址:
http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=csv&date=20210510&stockNo=2330
替換參數 「20210510」及「2330」 後就可以改變回傳內容。
此查詢結果會回傳 csv 格式的內容,內容包含股票當月各日成交資訊。
資料欄位包含日期/成交股數/成交金額/開盤價/最高價/最低價/收盤價/漲跌價差/成交筆數
接下來我就示範如何用程式取出欄位
<div class="panel panel-default">
<div class="panel-heading">範例3 當月各日成交資訊</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2">股票:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_SYMBOL_3.value">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">日期:</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="form.Q_MONTH_3.value">
</div>
</div>
<button type="button" class="btn btn-primary" v-on:click="GetMonthPrice()">查詢</button>
</div>
<div class="panel-footer">
<div class="table-responsive">
<table class="table">
<tr>
<th>日期</th>
<th>開盤價</th>
<th>最高價</th>
<th>最低價</th>
<th>收盤價</th>
<th>成交量</th>
</tr>
<tr v-for="(item, index) in gridMonthPirce.datas">
<td>{{item.date}}</td>
<td>{{item.open}}</td>
<td>{{item.high}}</td>
<td>{{item.low}}</td>
<td>{{item.close}}</td>
<td>{{item.volume}}</td>
</tr>
</table>
</div>
</div>
</div>
只向後端執行呼叫動作
// 當月各日成交資訊
, GetMonthPrice: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetMonthPrice")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.gridMonthPirce.datas = [];
self._RowDataBound(self.gridMonthPirce.datas, datas.gridList);
hideProcess();
}
}
});
}
利用程式組出要查詢的網址,接著使用 WebClient 取得回傳資料,取得資料是 CSV 格式,將 CSV 解析成 string[] 格式資料,就可以依索引位置來取得指定的資料。
/// <summary>
/// 當月各日成交資訊
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetMonthPrice(FormIn inModel)
{
GetMonthPriceOut outModel = new GetMonthPriceOut();
outModel.ErrorMsg = "";
// 呼叫網址
string download_url = "http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=csv&date=" + inModel.Q_MONTH_3 + "&stockNo=" + inModel.Q_SYMBOL_3;
string downloadedData = "";
using (WebClient wClient = new WebClient())
{
// 網頁回傳
downloadedData = wClient.DownloadString(download_url);
}
if (downloadedData.Trim().Length > 0)
{
outModel.gridList = new List<StockPriceRow>();
string[] lineStrs = downloadedData.Split('\n');
for (int i = 0; i < lineStrs.Length; i++)
{
string strline = lineStrs[i];
if (i == 0 || i == 1 || strline.Trim().Length == 0)
{
continue;
}
// 排除非價格部份
if (strline.IndexOf("說明") > -1 || strline.IndexOf("符號") > -1 || strline.IndexOf("統計") > -1 || strline.IndexOf("ETF") > -1)
{
continue;
}
ArrayList resultLine = new ArrayList();
// 解析資料
this.ParseCSVData(resultLine, strline);
string[] datas = (string[])resultLine.ToArray(typeof(string));
//檢查資料內容
if (Convert.ToInt32(datas[1].Replace(",", "")) == 0 || datas[3] == "--" || datas[4] == "--" || datas[5] == "--" || datas[6] == "--")
{
continue;
}
// 輸出資料
StockPriceRow row = new StockPriceRow();
row.date = datas[0]; //日期
row.open = datas[3]; //開盤價
row.high = datas[4]; //最高價
row.low = datas[5]; //最低價
row.close = datas[6]; //收盤價
row.volume = datas[1]; //成交量
outModel.gridList.Add(row);
}
}
// 輸出json
ContentResult resultJson = new ContentResult();
resultJson.ContentType = "application/json";
resultJson.Content = JsonConvert.SerializeObject(outModel); ;
return resultJson;
}
以上就是 3 種向證交所取得股價的實用方式,網頁上的程式碼中有些方法寫在共用方法區內,沒有展示出來,若需要了解更多可以下載完整範例。
付費後可下載此篇文章教學程式碼。
相關學習文章
[C#] 取得證交所上市及上櫃的股票及ETF清單(附範例)
[C#] 取得公開資訊觀測站股票基本資料(上市、上櫃、興櫃、公開發行) (附範例下載)
【C# 群益 API 開發教學】取得商品報價、Tick、最佳 5 檔教學 (附範例下載)
相關網站
WinTog 雲投資
如果都打算要寫了
建議你報名參加鐵人賽
可以報名永豐金API組
人比較少
比較有機會拿到大奬
可是我還沒用過永豐金的API,要馬上挑戰這 30 天 PO,有一點難度...
我相信以你群益API的經驗
一定沒問題的
(兩者都有提供 python 使用介面)